"""
https://www.yunpian.com/product/captcha
"""
# -*- coding: utf-8 -*-
# @Date    : 2024/2/17
# @Author  : zouy
# @Email: 874112501@qq.com

import string
import json
import random
import sys
import uuid
import time
from urllib import request
import os

from loguru import logger
import ddddocr
import requests
import execjs

sys.path.insert(0, os.path.dirname((os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))


from utils.ddddocr_case import Ddddocr
from utils.click_crack import ClickCrack


DIR_PATH = os.path.dirname(os.path.abspath(__file__))


from main.某片.gap import get_gap


def get_random(number):
    choices = string.ascii_lowercase + string.digits
    random_chars = random.choices(choices, k=number)
    return ''.join(random_chars)


def scale_sequence(last_value, index):
    sequence = [
        [913, 1976, 117], [914, 1976, 124], [918, 1976, 142], [931, 1976, 155], [948, 1976, 171],
        [970, 1976, 186], [986, 1977, 202], [999, 1979, 218], [1011, 1979, 234], [1017, 1979, 250],
        [1024, 1979, 267], [1031, 1979, 287], [1037, 1980, 301], [1042, 1980, 314], [1045, 1980, 330],
        [1049, 1980, 346], [1051, 1980, 362], [1052, 1980, 378], [1056, 1980, 395], [1057, 1980, 410],
        [1060, 1980, 427], [1064, 1980, 443], [1067, 1980, 458], [1071, 1980, 475], [1076, 1980, 490],
        [1080, 1980, 506], [1087, 1980, 523], [1092, 1980, 538], [1097, 1981, 554], [1100, 1981, 570],
        [1102, 1981, 586], [1105, 1981, 602], [1109, 1981, 618], [1113, 1981, 634], [1118, 1982, 650],
        [1124, 1983, 666], [1128, 1984, 682], [1130, 1984, 698], [1134, 1984, 714], [1137, 1984, 730],
        [1140, 1984, 746], [1141, 1985, 766], [1144, 1985, 787], [1146, 1985, 812], [1148, 1985, 828],
        [1149, 1985, 851], [1152, 1985, 899], [1154, 1985, 925], [1156, 1985, 947], [1157, 1985, 988],
        [1160, 1985, 1019], [1163, 1986, 1227], [1165, 1986, 1317], [1168, 1986, 1357], [1169, 1986, 1549],
        [1171, 1986, 1572], [1174, 1986, 1659], [1176, 1987, 1956], [1177, 1987, 2195], [1180, 1988, 2676],
        [1183, 1988, 3668], [1185, 1988, 3700],
    ]
    last_value += sequence[0][index]
    target_length = len(sequence)
    ratio = (last_value - sequence[0][index]) / (sequence[-1][index] - sequence[0][index])
    scaled_sequence = [int(sequence[0][index] + (sequence[i][index] - sequence[0][index]) * ratio) for i in
                       range(target_length - 1)]
    scaled_sequence.append(last_value)
    return scaled_sequence


def get_point(distance):
    x = scale_sequence(distance, 0)  # x轴坐标距离
    y = scale_sequence(3, 1)  # y轴坐标距离
    t = scale_sequence(1000 + distance / 400 * 1500, 2)  # 时间
    return [list(i) for i in (zip(x, y, t))]


def get_captcha(ss, ctx):
    cb = get_random(10)
    key = get_random(16)
    iv = get_random(16)
    yp_riddler_id = str(uuid.uuid4())

    # 1. 获取验证码图片 /captcha/get
    fp = '199b0f1085c5fa1b8b79727f034a6451'
    # fp = ctx.call('getFP')  # 生成指纹
    # print(fp)

    data = {
        "browserInfo": [
            {"key": "userAgent",
             "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"},
            {"key": "language", "value": "zh-CN"},
            {"key": "hardware_concurrency", "value": 8},  # 逻辑处理器核心数量，包含表示核心数的一个整数值
            {"key": "resolution", "value": [1536, 864]},  # window.screen.width   window.screen.height
            {"key": "navigator_platform", "value": "Win32"}
        ],
        "nativeInfo": None,
        "additions": {},
        "options": {
            "sdk": "https://www.yunpian.com/static/official/js/libs/riddler-sdk-0.2.2.js",
            "sdkBuildVersion": "1.5.0(2021111001)",
            "hosts": "https://captcha.yunpian.com"
        },
        "fp": fp,
        "address": "https://www.yunpian.com",
        "yp_riddler_id": yp_riddler_id
    }
    aes_i = ctx.call('AES_Encrypt', json.dumps(data, separators=(',', ':')), key, iv)
    rsa_k = ctx.call('RSA_Encrypt', key + iv)
    params = {
        "cb": cb,
        "i": aes_i,
        "k": rsa_k,
        "captchaId": captchaId
    }
    # print(params)
    url = 'https://captcha.yunpian.com/v1/jsonp/captcha/get'
    res = ss.get(url, params=params).text
    res = json.loads(res[res.index("(") + 1:res.rindex(")")])
    return (cb, key, iv, yp_riddler_id, fp, res)


def slide(captchaId):
    ss = requests.Session()
    ss.headers = {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Host": "captcha.yunpian.com",
        "Pragma": "no-cache",
        "Referer": "https://www.yunpian.com/",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
    }

    with open(os.path.join(DIR_PATH, 'yunpian.js'), 'r', encoding='utf-8') as f:
        ctx = execjs.compile(f.read())

    cb, key, iv, yp_riddler_id, fp, res = get_captcha(ss, ctx)

    # 2.缺口距离识别
    request.urlretrieve(res['data']['bg'], os.path.join(DIR_PATH, 'pic', 'bg.png'))
    request.urlretrieve(res['data']['front'], os.path.join(DIR_PATH, 'pic', 'slide.png'))
    distance = get_gap() - 5

    # 3.验证接口加密参数生成 /captcha/verify
    points = get_point(distance * 0.75)
    trace = {
        "points": points,  # 轨迹
        "distanceX": distance / 480,  # 移动坐标的比例(480为下载图片的长)
        "fp": fp,  # 指纹
        "address": "https://www.yunpian.com",
        "yp_riddler_id": yp_riddler_id  # 随机uuid
    }
    # print(json.dumps(trace, separators=(',', ':')))
    i = ctx.call('AES_Encrypt', json.dumps(trace, separators=(',', ':')), key, iv)
    k = ctx.call('RSA_Encrypt', key + iv)
    token = res['data']['token']
    params = {
        "cb": cb,
        "i": i,
        "k": k,
        'token': token,
        "captchaId": captchaId
    }
    url = "https://captcha.yunpian.com/v1/jsonp/captcha/verify"
    time.sleep(2)
    res = ss.get(url, params=params).text
    res_json = json.loads(res[res.index("(") + 1:res.rindex(")")])

    if res_json['code'] == 0:
        return res_json
    logger.info(f'失败：{res_json}')


def get_points(action=False, user_click_crack=True):
    """获取点坐标"""
    from ast import literal_eval as eval

    ocr = ddddocr.DdddOcr(beta=True, show_ad=False)

    with open(os.path.join(DIR_PATH, "pic", "click_words.png"), 'rb') as f:
        word_image = f.read()

    words = ocr.classification(word_image)  # 类孜顾
    print('需要点击的字符：', words)

    with open(os.path.join(DIR_PATH, "pic", "click_captcha.png"), 'rb') as f:
        captcha_image = f.read()
    point_dict = Ddddocr().case_demo(captcha_image)
    print('验证码字符坐标', point_dict)  # {'顾': (87, 165), '弦': (244, 164), '赤': (311, 47), '类': (225, 66)}

    result = [point_dict.pop(word, None) for word in words]
    for idx, w in enumerate(result):
        if w:
            continue
        if len(point_dict) == 1:
            result[idx] = tuple(point_dict.values())[0]
            break
        else:
            if user_click_crack:
                result = ClickCrack(bg=captcha_image).discern()
                break
            else:
                print(f'"{words[idx]}" 未找到')
                if not action:
                    return []
                # 手动输入
                result[idx] = tuple(eval('(' + input(f'请输入"{words[idx]}"坐标(x,y以逗号分隔)') + ')'))

    logger.info(result)
    return result


def click(captcha_id, action=False):
    """点选"""
    ss = requests.Session()
    ss.headers = {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Host": "captcha.yunpian.com",
        "Pragma": "no-cache",
        "Referer": "https://www.yunpian.com/",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
    }

    with open(os.path.join(DIR_PATH, 'yunpian.js'), 'r', encoding='utf-8') as f:
        ctx = execjs.compile(f.read())

    cb, key, iv, yp_riddler_id, fp, res = get_captcha(ss, ctx)
    os.path.exists(os.path.join(DIR_PATH, 'pic')) or os.mkdir(os.path.join(DIR_PATH, 'pic'))
    request.urlretrieve(res['data']['captchaImage'], os.path.join(DIR_PATH, 'pic', 'click_captcha.png'))
    request.urlretrieve(res['data']['wordsImage'], os.path.join(DIR_PATH, 'pic', 'click_words.png'))

    # 3.验证接口加密参数生成 /captcha/verify
    # points = [
    #     [282.375, 119.17500305175781],
    #     [204.375, 42.17500305175781],
    #     [251.375, 67.17500305175781]
    # ]
    points = get_points(action)
    if not points:
        logger.warning('验证码识别失败\n')
        return
    points = [[x / 480, y / 240] for x, y in points]
    i_params = {
        "points": points,
        "fp": fp,
        "address": "https://www.yunpian.com",
        "yp_riddler_id": yp_riddler_id
    }
    logger.info(f'请求参数：{i_params}')
    i = ctx.call('AES_Encrypt', json.dumps(i_params, separators=(',', ':')), key, iv)
    k = ctx.call('RSA_Encrypt', key + iv)
    token = res['data']['token']
    params = {
        "i": i,
        "k": k,
        "cb": cb,
        'token': token,
        "captchaId": captcha_id
    }
    url = "https://captcha.yunpian.com/v1/jsonp/captcha/verify"
    time.sleep(2)
    res = ss.get(url, params=params).text
    res_json = json.loads(res[res.index("(") + 1:res.rindex(")")])

    if res_json['code'] == 0:
        return res_json
    logger.info(f'失败：{res_json}')


if __name__ == '__main__':
    # get_points(True)

    # ####### 点选
    # captchaId = '0f6b75adb74b4f62a364b55b6de3b02c'
    # action = False  # 是否手动输入验证码坐标
    # for _ in range(10):
    #     result = click(captchaId, action)
    #     if result:
    #         logger.success(f'成功：{result}')
    #         break
    #     time.sleep(1)
    # else:
    #     logger.error('运行失败')

    # ######## 滑块
    captchaId = '974cd565f11545b6a5006d10dc324281'
    for _ in range(10):
        result = slide(captchaId)
        if result:
            logger.success(f'成功：{result}')
            break
    else:
        logger.error('运行失败')
